﻿using System;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Quakk.Twitter;

namespace Quakk.Controls
{
    public class TweetPanel : Panel 
    {
        private const int minimumHeight = 66;

        private int oldWidth;
        private Bitmap bitmap;
        private bool isDirty = true;

        public Tweet Tweet
        {
            get; set;
        }

        protected Control ParentControl
        {
            get; set;
        }

        public TweetPanel(Control parentControl, Tweet tweet)
        {
            Tweet = tweet;
            ParentControl = parentControl;
            Height = minimumHeight;
            Width = 240;

            bitmap = new Bitmap(Width, Height);

            Size size = GetMessageTextSize();
            if(size.Height + 40 > Height)
            {
                Height = size.Height + 40;
            }

            Tweet.UserImageChanged += UserImageChanged;
        }

        /// <summary>
        /// Sets the Width of the TweetPanel and adjusts the Height to fit the content
        /// </summary>
        /// <param name="width"></param>
        public void SetWidth(int width)
        {
            Width = width;

            if (ParentControl != null && Width != oldWidth)
            {
                Size size = GetMessageTextSize();
                if (size.Height + 40 > minimumHeight)
                {
                    Height = size.Height + 40;
                }
                else
                {
                    Height = minimumHeight;
                }
                oldWidth = Width;

                // Re-create the bitmap used to double buffer
                // and mark as dirty to force it to redraw
                bitmap = new Bitmap(Width, Height);
                isDirty = true;
            }
        }

        protected void UserImageChanged(object sender, EventArgs e)
        {
            isDirty = true;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            // Simple version of double-buffering
            if(isDirty)
            {
                Graphics gfx = Graphics.FromImage(bitmap);
                DrawPanelGraphics(gfx);
                gfx.Dispose();
            }
            
            e.Graphics.DrawImage(bitmap, 0, 0);
        }

        /// <summary>
        ///  Overriden to help prevent flicker
        /// </summary>
        protected override void OnPaintBackground(PaintEventArgs e)
        {
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if(bitmap != null)
            {
                bitmap.Dispose();
            }
        }

        /// <summary>
        /// Calculate the size of the Message text when rendered in place at the current Width
        /// </summary>
        /// <returns>The Size of the text</returns>
        private Size GetMessageTextSize()
        {
            string message = Tweet.Message;

            // Create a new bitmap to draw the message onto
            RectangleF bodyRectangle = new RectangleF(0, 0, Width - 65, 100);
            Bitmap bmp = new Bitmap((int)bodyRectangle.Width, (int)bodyRectangle.Height);
            Graphics gfx = Graphics.FromImage(bmp);
            gfx.Clear(Color.White);

            // Draw the message onto the bitmap using an Underlined style
            Font font = Display.BodyFont;
            font = new Font(font.Name, font.Size, FontStyle.Underline);
            Brush brush = new SolidBrush(Color.Black);
            gfx.DrawString(message, font, brush, bodyRectangle);
            brush.Dispose();
            font.Dispose();

            // Check each pixel second from the left from the bottom up until we hit a
            // non-white pixel - then we've hit the underline and know the height of the text
            int result = 0;
            for (int i = 99; i > 0; i--)
            {
                Color color = bmp.GetPixel(2, i);
                if (!color.Equals(Color.White))
                {
                    result = i;
                    break;
                }
            }

            gfx.Dispose();
            bmp.Dispose();

            return new Size(Width, result);
        }

        /// <summary>
        /// Draws the graphics that make up this TweetPanel to the specified Graphics context
        /// </summary>
        /// <param name="gfx">The Graphics context to draw to</param>
        private void DrawPanelGraphics(Graphics gfx)
        {
            string message = Tweet.Message;
            string status = "on " + Tweet.TweetDateTime + " via " + RemoveHtmlTags(Tweet.Source);
            Rectangle panelRectangle = new Rectangle(0, 0, Size.Width, Size.Height);

            // Draw Panel Background
            Brush background;
            Color backgroundColor;
            if (Tweet.IsReply)
            {
                background = Display.ReplyTweetBrush;
                backgroundColor = Display.ReplyTweetColour;
            }
            else if (Tweet.IsSelfTweet)
            {
                background = Display.SelfTweetBrush;
                backgroundColor = Display.SelfTweetColour;
            }
            else
            {
                background = Display.StandardTweetBrush;
                backgroundColor = Display.StandardTweetColour;
            }
            gfx.FillRectangle(background, panelRectangle);
            background.Dispose();

            // Draw the highlight along the top of the tweet
            DrawHorizGradient(gfx, backgroundColor, 39, 5, Height/2, 2, Width);

            // Draw the shadow below the tweet
            Color shadowColor = Display.ShadowColour;
            DrawHorizGradient(gfx, shadowColor, 0, 51, 4, Height - 4, Width);

            // Draw Panel Border
            Pen border = Display.BorderPen;
            gfx.DrawLine(border, 0, 0, Width, 0);
            gfx.DrawLine(border, 0, Height - 5, Width, Height - 5);
            border.Dispose();

            // Draw Username
            Font titleFont = Display.TitleFont;
            Brush titleBrush = Display.TitleBrush;
            float titleHeight = gfx.MeasureString(Tweet.Username, titleFont).Height;
            gfx.DrawString(Tweet.Username, titleFont, titleBrush, 59, 5);
            titleBrush.Dispose();
            titleFont.Dispose();

            // Draw Message
            RectangleF bodyRectangle = new RectangleF(59, 5 + titleHeight, Width - 65, Height);
            Font bodyFont = Display.BodyFont;
            Brush bodyBrush = Display.BodyBrush;
            gfx.DrawString(message, bodyFont, bodyBrush, bodyRectangle);
            bodyBrush.Dispose();
            bodyFont.Dispose();

            // Draw Footer
            Font footerFont = Display.FooterFont;
            Brush footerBrush = Display.FooterBrush;
            gfx.DrawString(status, footerFont, footerBrush, 59, Height - 18);
            footerBrush.Dispose();
            footerFont.Dispose();

            // Draw User Image
            if (Tweet.UserImage == null)
            {
                Brush brush = new SolidBrush(Color.Black);
                gfx.FillRectangle(brush, 6, 9, 48, 48);
                brush.Dispose();
            }
            else
            {
                Pen imageBorder = Display.ImageBorderPen;
                gfx.DrawImage(Tweet.UserImage, 6, 9);
                gfx.DrawRectangle(imageBorder, 5, 8, 49, 49);
                imageBorder.Dispose();
            }

            isDirty = false;
        }

        /// <summary>
        /// Draws a series of horizontal lines across the image in shades of colour that vary from the supplied colour
        /// </summary>
        /// <param name="gfx">The Graphics context to draw to</param>
        /// <param name="color">The base colour</param>
        /// <param name="startVariation">An integer offset for the start shade of colour</param>
        /// <param name="endVariation">An integer offset for the end shade of colour</param>
        /// <param name="height">The number of lines to draw</param>
        /// <param name="y">The y co-ordinate to start drawing the lines</param>
        /// <param name="width">The width to draw the lines</param>
        private static void DrawHorizGradient(Graphics gfx, Color color, int startVariation, int endVariation, int height, int y, int width)
        {
            double[] variations = new double[height];
            double step = (double) (startVariation - endVariation)/height;

            for (int i = 0; i < height; i++)
            {
                variations[i] = startVariation - (step * i);                
            }

            Pen pen = new Pen(color);
            for (int i = 0; i < variations.Length; i++)
            {
                pen.Color = Display.IncreaseLuminance(color, (int)variations[i]);
                gfx.DrawLine(pen, 0, y + i, width, y + i);
            }
            pen.Dispose();
        }

        /// <summary>
        /// Removes any Html tags from the string provided
        /// </summary>
        /// <param name="text">The string to remove from</param>
        /// <returns>The resulting string</returns>
        private static string RemoveHtmlTags(string text)
        {
            return text != null ? Regex.Replace(text, "(<.*?>)", "") : "";
        }
    }
}
